/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID coidgen tool.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Robo Strycek
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */



#include "coid/comm/commassert.h"
#include "coidgen.h"
#include "os.h"



using namespace coid;



extern char TEMPL_OPEN_SVC_INIT[];
extern char TEMPL_OPEN_SVC_SETUP[];




enum template_parts {
	tp_includes=0, tp_static, tp_fnc_enum, tp_fnc_flags, tp_fnc_flags_end,
	tp_fnc_defines, tp_init_table, tp_init_table_end,
	tp_fnc_body, tp_fnc_body0, tp_fnc_body1, tp_fnc_body2,
    tp_fnc_body0_cc, tp_fnc_body1_cc, tp_fnc_body2_cc,

	tp_accept_connect_within, tp_accept_connect, tp_accept_connect_shared,
	tp_fnc_connect_within0, tp_fnc_connect_within1, tp_fnc_connect_within2,
	tp_fnc_connect_body0, tp_fnc_connect_body1, tp_fnc_connect_body2,

	tp_x_client, tp_client_fnc,
	tp_eof,

	tp_last
};






int H_FILE::write_client_cpp( char * templ, charstr & out, uint class_index )
{
	if( class_index >= cls.size() )
		return 1;
	CLASS * c = cls[class_index];

	templ_parsed.reset();
	char * ptr = templ;

	while( 1 ) {
		char * p = strstr( ptr, "$$$$" );
		if(p) *p = 0;
		*templ_parsed.add(1) = ptr;

		if(!p) break;
		ptr = p + 4;
		if( *ptr == '\r' ) ptr++;
		if( *ptr == '\n' ) ptr++;
	}



	if( templ_parsed.size() < tp_last ) {
		printf( "Invalid template !\n" ); DASSERT( 0 && "Invalid template !" );
		return 1;
	}
	else if( templ_parsed.size() > tp_last )
		printf( "There are too many template subsections ! (%ld : %d)\n", templ_parsed.size(), tp_last );



	/// basic includes, #defines
	replace_template( false, templ_parsed[tp_includes], out, c );

	/// basic constants, version, name, ...
	replace_template( false, templ_parsed[tp_static], out, c );



	/// enum
	replace_template( false, templ_parsed[tp_fnc_enum], out, c );

	dynarray<FUNCTION *> & fnc = c->fnc_export;
	charstr tmp;
	uint i, count;
	for( i=0, count=0; i<fnc.size(); i++ ) {
		fnc[i]->get_id_name( tmp );
		if(count) out += "," EOL;
		out += "\t";
		out += tmp;
		out += " = ";
		out += fnc[i]->id;
		count++;
	}
	out += EOL;
	replace_template( false, templ_parsed[tp_fnc_flags], out, c );




	/// flags
	char flags[32];
	for( i=0; i<c->max_fnc_index; i++ ) {
		if(i) out << "," EOL;
		uint f;
		for( f=0; f<fnc.size(); f++ ) {
			if( fnc[f]->id == i )
				break;
		}
		if( f < fnc.size() )		/// found
			sprintf( flags, "0x%X", fnc[f]->flags & FUNCTION::f_visible_flags );
		else strcpy( flags, "0" );				/// not implemented
		out << "\t" << flags;
	}
	if( ! c->max_fnc_index ) out << "\t0";		/// prevent uint an_array[] { from being empty };

	replace_template( false, templ_parsed[tp_fnc_flags_end], out, c );



	/// local client class
	c->stream_to( out, T_client_class_in_cpp );


	replace_template( false, templ_parsed[tp_fnc_defines], out, c );




	/// fill function table
	replace_template( false, templ_parsed[tp_init_table], out, c );
	/// set disconnect() function pointer
	out << "\tCOID_CLIENT_CREATE_DISCONNECT_HOOK( coid_table_index++ )" EOL;
	uint id = 0;
	for( i=0; i<c->max_fnc_index; i++ ) {
		if( id < c->fnc_export.size() && i == c->fnc_export[id]->id ) {
			if( (c->fnc_export[id]->flags & FUNCTION::fcomm_mask) == FUNCTION::fdirect ) {
				out << "\t((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &" << c->name << "_localclient::coid_throw;";
				out << "  // '" << c->fnc_export[id]->name << "' is direct only" EOL;
			}
			else if( c->fnc_export[id]->handle_client_calls ) {
				out << "\t((coid_fnc *) table)[coid_table_index++] = (coid_fnc) &" << c->name << "_localclient::coid_throw;";
				out << "  // '" << c->fnc_export[id]->name << "' is not called through this table" EOL;
			}
			else {
				//out << "\tCOID_CLIENT_CREATE_HOOK( " << c->fnc_export[id]->name << ", " << i << ", coid_table_index++ )" EOL;
				out << "\tCOID_CLIENT_CREATE_HOOK( " << c->fnc_export[id]->name << ", " << i << ", coid_table_index++, ";
				c->fnc_export[id]->stream_to( out, T_client_function_hook );
				out << " )" EOL;
			}

			id++;
		}
	}

	replace_template( false, templ_parsed[tp_init_table_end], out, c );



	/// add function calls for every function in *_localclient class
	uint f;
	for( f=0; f<fnc.size(); f++ ) {
		if( (fnc[f]->flags & FUNCTION::fcomm_mask) != FUNCTION::fdirect ) {
			replace_template( false, templ_parsed[tp_fnc_body], out, c, f );
		}
		else {
			out << "// method '" << fnc[f]->name << "' is direct only" EOL;
		}
	}



	/// write common methods for client
	replace_template( false, templ_parsed[tp_x_client], out, c );



	/// special functions
	/// do not generate connect() method for client for class with accept_auto_connect() method and missing accept_connect() method
	FUNCTION * fn = c->fnc_special[CLASS::accept_connect];
	if( ! ((c->flags & CLASS::fautorun) && (fn->flags & FUNCTION::fnot_present_in_service)) )
	{
		replace_template( false, templ_parsed[tp_accept_connect], out, c, 0x80000000 | CLASS::accept_connect );
		fn->flags |= FUNCTION::faccept_connect_within;
		replace_template( false, templ_parsed[tp_accept_connect_within], out, c, 0x80000000 | CLASS::accept_connect );
		fn->flags &= ~FUNCTION::faccept_connect_within;
	}

	replace_template( false, templ_parsed[tp_accept_connect_shared], out, c, 0x80000000 | CLASS::accept_connect_shared );



	/// stream *_client methods
	out << EOL EOL EOL;
	for( f=0; f<fnc.size(); f++ ) {
		fnc[f]->stream_to( out, T_client_cpp_inline_fnc );
		out << EOL EOL;
	}


	replace_template( false, templ_parsed[tp_client_fnc], out, c );


	/// write the rest at the end of file
	replace_template( false, templ_parsed[tp_eof], out, c );




	return 0;
}










void H_FILE::replace_client_template( uint templ_index, charstr & out, const CLASS * c, uint fnc_index ) const
{
	DASSERT( fnc_index != UMAX32 );

	FUNCTION * f;
	uint body_template = tp_fnc_body0_cc;
	uint body_template1 = tp_fnc_body1_cc;
	uint body_template2 = tp_fnc_body2_cc;

    bool cci=true;
	if( fnc_index & 0x80000000 ) {	/// special fnc
		f = c->fnc_special[fnc_index & ~0x80000000];
		DASSERT( f );
		if( (fnc_index & ~0x80000000) == CLASS::accept_connect ) {
			body_template = tp_fnc_connect_body0; body_template1 = tp_fnc_connect_body1; body_template2 = tp_fnc_connect_body2;
			if( f->flags & FUNCTION::faccept_connect_within ) {
				body_template = tp_fnc_connect_within0; body_template1 = tp_fnc_connect_within1; body_template2 = tp_fnc_connect_within2;
			}
            cci=false;
		}
		else if( (fnc_index & ~0x80000000) == CLASS::accept_connect_shared ) {
			body_template = tp_fnc_connect_body0; body_template1 = tp_fnc_connect_body1; body_template2 = tp_fnc_connect_body2;
            cci=false;
		}
	}
	else {
		if( fnc_index >= c->fnc_export.size() )
			DASSERT( fnc_index < c->fnc_export.size() );
		f = c->fnc_export[fnc_index];
	}

	charstr tmp;
	f->get_id_name( tmp );

	switch( STR_templates[templ_index].flags ) {		/// flags ~ id
		/// fnc_id_name
		case 1000: {
			uint index = fnc_index & ~0x80000000;
			if( fnc_index & 0x80000000 ) {
				if( index == CLASS::accept_connect ) out << "StdDispatchMethod::AUTHENTIFY";
				else if( index == CLASS::accept_connect_shared ) out << "StdDispatchMethod::AUTHENTIFY_SHARED";
			}
			else out << tmp;
		} break;
		/// fnc_client_body
		case 2000: {
			/// function header:
			f->stream_to( out, T_client_cpp );
			out << EOL "{" EOL;
			replace_template( false, templ_parsed[body_template], out, c, fnc_index );

			/// stream input params:
			f->client_streaming_to_stream( out, cci );

			replace_template( false, templ_parsed[body_template1], out, c, fnc_index );

			/// stream output params:
			f->client_streaming_from_stream( out, cci );

			replace_template( false, templ_parsed[body_template2], out, c, fnc_index );
		} break;
		//fnc_client_retval_declaration
		case 2010: {
			if( f->void_return_type() || f->opcd_return_type() ) {
				out.trim();
				return;
			}
			else out << f->ret.type;
			out << " ";
			if( f->ret.asterix_count == 1 ) {
				DASSERT( f->ret.asterix_count == 1 );
				out << "* ";
			}
			out << STR_coid_return_value << ";";
		} break;
		//fnc_client_set_error_ret_value
		case 2020: {
			if( f->void_return_type() || f->opcd_return_type() ) out.trim();
			else out << STR_coid_return_value << " = 0;";
		} break;
		//fnc_client_return
		case 2100:
			if( f->void_return_type() ) out << "return;";
			else if( f->opcd_return_type() ) out << "return _coid_err_code;";
			else out << "return _coid_return_value;";
		break;
		//fnc_client_return_error
		case 2101:
			if( f->void_return_type() ) out << "return;";
			else if( f->opcd_return_type() ) out << "return ersFE_DISCONNECTED;";
			else out << "return 0;";
		break;
		//fnc_client_return_cc
		case 2102:
			if( f->void_return_type() ) out << "return;";
			else if( f->opcd_return_type() ) out << "return cc._coid_err_code;";
			else out << "return cc._coid_return_value;";
		break;
		//fnc_client_shared_params
		case 2200:
			if( (fnc_index & ~0x80000000) == CLASS::accept_connect_shared ) out << "coid_obj_id";
			else out << "UMAX32";
		break;

		//ib_ret_val_name
		case 4000: {
			if( ! f->void_return_type() ) {
				const char * var_name = "_coid_err_code";
				if( ! f->opcd_return_type() ) var_name = STR_coid_return_value;
				out << var_name << " = ";
			}
		} break;
		//ib_open_service1
		case 4010:
			if( f->open_service.len() )		/// add code that sets up stream of client
				replace_template( false, TEMPL_OPEN_SVC_INIT, out, c, fnc_index );
		break;
		//ib_open_service2
		case 4020: {
			if( f->open_service.len() )		/// add code that sets up stream of client
				replace_template( false, TEMPL_OPEN_SVC_SETUP, out, c, fnc_index );
		} break;
		//open_service_arg_name
		case 4030: {
			uint j;
			for( j=0; j<f->args.size(); j++ )
				if( f->args[j].flags & ARG::fopen_service )
					break;
			RASSERT( j < f->args.size() );	/// internall error, should never happen
			out << f->args[j].name;
		} break;
		//open_service_client_name
		case 4040: {
			out << "__coid_" << f->open_service << "_client";
		} break;
		//ib_method_args
		case 4050: {
			const ARG * a = f->args.ptr();
			for( uint i=0; i<f->args.size(); i++, a++ ) {
				if( a->type_remap ) out << a->type_remap->name;
				else out << a->name;
				if( i < f->args.size() - 1 ) out << ", ";
			}
		} break;
		//ib_method_fetch_args
		case 4060: {
			const ARG * a = f->args.ptr();
			for( uint i=0; i<f->args.size(); i++, a++ ) {
				const ARG * b = a;
				if( a->type_remap ) b = a->type_remap;
				if( a->flags & ARG::ffetch ) {
					if( b->asterix_count == 2 ) out << "&";
					out << "coidfetch_";
				}
				out << b->name;
				if( i < f->args.size() - 1 ) out << ", ";
			}
		} break;

		//ib_method_call
		case 4100: {
			if( ! f->handle_client_calls ) {
				replace_template( false, "\t\t%ib_ret_val_name%", out, c, fnc_index );
				out << "(_me->*(_vtbl->" << f->name << "__" << f->id << ")) (";
				replace_template( false, "%ib_method_args%);" EOL, out, c, fnc_index );
			}
			else {
				if( (f->flags & FUNCTION::fcomm_mask) != FUNCTION::fdirect ) {
					out << "\t\tif( get_connection_type() != ConnectFlags::fACCESS_MODE_DIRECT )" EOL;
					replace_template( false, "\t\t\t%ib_ret_val_name%", out, c, fnc_index );
					out << "((" << c->name << "_localclient *) _me)->" << f->name << " (";
					replace_template( false, "%ib_method_args%);" EOL, out, c, fnc_index );

					out << "\t\telse {" EOL;
				}

				if( f->handle_client_calls == 2 ) {		/// there are only fetch arguments ==> use direct call + copy fetch args
					const char * tabs = "\t\t\t";
					/// init fetch args:
					const ARG * a = f->args.ptr();
					uint i;
					for( i=0; i<f->args.size(); i++, a++ ) {
						const ARG * b = a;
						if( a->type_remap ) b = a->type_remap;
						if( ! (b->flags & ARG::ffetch) ) continue;
						out << tabs << b->type << " * coidfetch_" << b->name << ";" EOL;
					}
					/// method call
					replace_template( false, "\t\t\t%ib_ret_val_name%", out, c, fnc_index );
					out << "(_me->*(_vtbl->" << f->name << "__" << f->id << ")) (";
					replace_template( false, "%ib_method_fetch_args%);" EOL, out, c, fnc_index );
					out << tabs << "if(!_coid_err_code) {" EOL;

					/// copy fetch arguments
					const char * tabs2 = "\t\t\t\t";
					a = f->args.ptr();
					for( i=0; i<f->args.size(); i++, a++ ) {
						const ARG * b = a;
						if( a->type_remap ) b = a->type_remap;
						if( ! (b->flags & ARG::ffetch) ) continue;
						if( b->flags & ARG::fptr_count ) {		/// copy for fetch argument with count
							charstr tmp_type;
							H_FILE::remove_const( b->type, tmp_type );
							// alloc

							charstr tmp;
							if( ! (b->flags & ARG::fref) ) tmp << "*";
							b->append_client_arg_name( tmp, ARG::adt_name );
							out << tabs2;
							out << tmp;
							if( b->flags & ARG::fmalloc )
								out << " = malloc( sizeof(" << tmp_type << ") * (uint) " << b->count_string << " );" EOL;
							else
								out << " = new " << tmp_type << "[ (uint) " << b->count_string << " ];" EOL;

							// copy
							out << tabs2 << "for( uint _coid_i=0; _coid_i < (uint)" << b->count_string << "; _coid_i++ ) {" EOL;
							out << tabs2 << "\t(" << tmp << ")";
							out << "[_coid_i] = coidfetch_" << b->name << "[_coid_i];" EOL;
							out << tabs2 << "}" EOL;
						}
						else {
							out << tabs2;
							if( b->asterix_count == 2 ) out << "*";
							out << b->name << " = *coidfetch_" << b->name << ";" EOL;
						}
					}
					out << tabs << "}" EOL;
				}
				else {			/// remaps ==> stream to exestream && read output from it
					out << "\t\t\tCOID_CLIENT_EXESTREAM_ARGS args( (void *) _me, ";
					replace_template( true, "%fnc_id_name% );" EOL, out, c, fnc_index );
					out << "\t\t\t_mexestream.reset();" EOL;
					out << "\t\t\t_mexestream.set_fnc( (mexestream::mexestream_fnc) (_vtbl->" << f->name << "__" << f->id << "), &args );" EOL;
					/// aliases:
					out << "\t\t\tmexestream * _coid_bstream = &_mexestream;" EOL;
					if( ! f->opcd_return_type() ) out << "\t\t\topcd _coid_err_code;" EOL;

					/// stream input params, flush, stream output params:
					charstr tmp_out;
					f->client_streaming_to_stream( tmp_out, false );
					replace_template( false, templ_parsed[tp_fnc_body1], tmp_out, c, fnc_index );
					f->client_streaming_from_stream( tmp_out, false );

					/// insert more tabs:
					const char * tabs = "\t\t";
					const char * p = tmp_out.ptr();
					while(1) {
						const char * p2 = strchr( p, '\n' );
						if(!p2) break;
						while( *p2 == '\r' || *p2 == '\n' ) p2++;
						p2--;
						char c = *p2;
						*(char*)p2 = 0;
						out << tabs << p << c;
						p = p2 + 1;
					}
					out << p;
					out << "\t\t\t_coid_bstream->acknowledge();" EOL;
				}

				if( (f->flags & FUNCTION::fcomm_mask) != FUNCTION::fdirect )
					out << "\t\t}" EOL;
			}
		} break;

		default: printf( "Template parameter '%s' not implemented !\n", STR_templates[templ_index].name ); DASSERT( 0 ); break;
	}
}










void ARG::append_client_arg_name( charstr & out, ARG_DISPATCH_TYPE type ) const
{
	switch( type ) {
		case adt_name: out << name; break;

		case adt_count_name:
			if( arg_count == UMAX32 ) out << count_string; 
			else out << arg_count_eval->name;
		return;

		default: DASSERT( 0 ); break;
	}
}




